home *** CD-ROM | disk | FTP | other *** search
/ GameStar 2004 April / Gamestar_61_2004-04_dvdb.iso / DVDStar / Editace / hltp.exe / {app} / Applications / QuArK / quarkpy / qhandles.py < prev    next >
Text File  |  2004-01-05  |  50KB  |  1,584 lines

  1. """   QuArK  -  Quake Army Knife
  2.  
  3. Generic Mouse handles code.
  4. """
  5. #
  6. # Copyright (C) 1996-99 Armin Rigo
  7. # THIS FILE IS PROTECTED BY THE GNU GENERAL PUBLIC LICENCE
  8. # FOUND IN FILE "COPYING.TXT"
  9. #
  10.  
  11. #$Header: /cvsroot/quark/runtime/quarkpy/qhandles.py,v 1.9 2002/05/18 09:51:56 tiglari Exp $
  12.  
  13. #
  14. # Generic handles. See maphandles.py for more information.
  15. # This module defines only generic handle classes that are
  16. # not related to map editing.
  17. #
  18.  
  19.  
  20. from qeditor import *
  21. from qdictionnary import Strings
  22. import qmenu
  23.  
  24.  
  25. MOUSEZOOMFACTOR = math.sqrt(2)     # with this value, the zoom factor doubles every two click
  26. STEP3DVIEW = 64.0
  27.  
  28. vfSkinView = 0x80 # 2d only - used for skin page for mdl editor and bezier page for map editor
  29.  
  30. #
  31. # Global variables that are set by the map editor.
  32. #
  33. # There are two grid values : they are the same, excepted when
  34. # there is a grid but disabled; in this case, the first value
  35. # is 0 and the second one is the disabled grid step - which is
  36. # used only if the user hold down Ctrl while dragging.
  37. #
  38.  
  39. grid = (0,0)
  40. lengthnormalvect = 0
  41. mapicons_c = -1
  42.  
  43.  
  44. def aligntogrid(v, mode):
  45.     #
  46.     # mode=0: normal behaviour
  47.     # mode=1: if v is a 3D point that must be forced to grid (when the Ctrl key is down)
  48.     #
  49.     g = grid[mode]
  50.     if g<=0.0:
  51.         return v   # no grid
  52.     rnd = quarkx.rnd
  53.     return quarkx.vect(rnd(v.x/g)*g, rnd(v.y/g)*g, rnd(v.z/g)*g)
  54.  
  55. def setupgrid(editor):
  56.     #
  57.     # Set the grid variable from the editor's current grid step.
  58.     #
  59.     global grid
  60.     grid = (editor.grid, editor.gridstep)
  61.  
  62. def cleargrid():
  63.     global grid
  64.     grid = (0,0)
  65.  
  66.  
  67.  
  68. #
  69. # Angle-aligning function.
  70. #
  71.  
  72. def alignanglevect(v, mode):
  73.     anglestep = quarkx.setupsubset(mode, "Building")["ForceAngleStep"][0]
  74.     pitch,roll,yaw = vec2angles1(v)
  75.     pitch = quarkx.rnd(pitch/anglestep)*anglestep
  76.     roll = quarkx.rnd(roll/anglestep)*anglestep
  77.     return angles2vec1(pitch, roll, yaw)
  78.  
  79.  
  80. #
  81. # Various angle conversion functions.
  82. # angles are a string of 3 numbers : "pitch roll yaw" in degrees
  83. #
  84.  
  85. def vec2angles1(v):
  86.     if not v:
  87.         return (0,0,0)
  88.     if v.z:
  89.         hlen = math.sqrt(v.x*v.x+v.y*v.y)
  90.         pitch = math.atan2(-v.z, hlen) * rad2deg
  91.         if not hlen:
  92.             return (pitch,0,0)
  93.     else:
  94.         pitch = 0
  95.     return (pitch, math.atan2(v.y,v.x) * rad2deg, 0)
  96.  
  97. def vec2angles(v, oldvalue=None):
  98.     pitch, roll, yaw = vec2angles1(v)
  99.     if oldvalue is not None:
  100.         try:
  101.             yaw = quarkx.vect(oldvalue).z
  102.         except:
  103.             pass
  104.     return str(quarkx.vect(pitch, roll, yaw))
  105.  
  106. def vec2angle(v, oldvalue=None):
  107.     hlen2 = v.x*v.x+v.y*v.y
  108.     if hlen2 > v.z*v.z:
  109.         angle = quarkx.rnd(math.atan2(v.y,v.x) * 180.0/math.pi)
  110.         if angle>0:
  111.             return `angle`
  112.         return `angle+360`
  113.     if v:
  114.         if v.z > 0.0:
  115.             return "-1"    # up
  116.         else:
  117.             return "-2"    # down
  118.     return "0"
  119.  
  120. def angles2vec1(pitch, roll, yaw):
  121.     roll = roll * deg2rad
  122.     pitch = -pitch * deg2rad
  123.     sin = math.sin
  124.     cos = math.cos
  125.     cosb = cos(pitch)
  126.     return quarkx.vect(cos(roll)*cosb, sin(roll)*cosb, sin(pitch))
  127.  
  128. def angles2vec(s):
  129.     return apply(angles2vec1, quarkx.vect(s).tuple)
  130.  
  131. def angle2vec(angle):
  132.     angle = int(angle)
  133.     if angle==-1:
  134.         return quarkx.vect(0,0,1)
  135.     if angle==-2:
  136.         return quarkx.vect(0,0,-1)
  137.     angle = angle * math.pi/180.0
  138.     return quarkx.vect(math.cos(angle), math.sin(angle), 0)
  139.  
  140.  
  141.  
  142. #
  143. # The handle classes.
  144. #
  145.  
  146. class GenericHandle:
  147.     "A handle on a view that can be grabbed with the mouse."
  148.  
  149.     #
  150.     # a string used for the undo texts
  151.     #
  152.     undomsg = Strings[514]
  153.  
  154.     #
  155.     # a string for the hint boxes
  156.     #
  157.     hint = ""
  158.  
  159.     def __init__(self, pos):
  160.         self.pos = pos
  161.         self.cursor = CR_DEFAULT     # default mouse cursor
  162.  
  163.     def draw(self, view, cv, draghandle=None):
  164.         "Draw a handle on the given view."
  165.         pass    # abstract
  166.  
  167.     def drawred(self, redimages, view, redcolor):
  168.         "Draw a handle while it is being dragged."
  169.         pass    # abstract
  170.  
  171.     def drag(self, v1, v2, flags, view):
  172.         "Drag a handle."
  173.         #
  174.         # This method must return two lists :
  175.         #  * the old objects
  176.         #  * the new objects that replace them
  177.         #
  178.         return None, None    # abstract
  179.  
  180.     #
  181.     # For setting stuff up at the beginning of a drag
  182.     #
  183.     def start_drag(self, view, x, y):
  184.       pass
  185.  
  186.     #
  187.     # old, new allow plugins etc to define extra stuff
  188.     #  to be done before committal of changes
  189.     #
  190.     def ok(self, editor, undo, old, new):
  191.       editor.ok(undo, self.undomsg)
  192.  
  193.     def menu(self, editor, view):
  194.         "A pop-up menu for the handle."
  195.         return []    # abstract
  196.  
  197.     def click(self, editor):
  198.         "Handle a simple click."
  199.         #
  200.         # Usually ignored - the click is passed
  201.         # through the handle and selects the next object.
  202.         #
  203.         pass
  204.  
  205.     def leave(self, editor):
  206.         # See maphandles.py.
  207.         pass
  208.  
  209.     def getdrawmap(self):
  210.         #
  211.         # Special behaviour is required for special cases only,
  212.         # see maphandles.py.
  213.         #
  214.         return None, refreshtimer     # default behaviour
  215.  
  216.     def Action(self, editor, v1, v2, flags, view, undomsg=None):
  217.         "Simulates a drag of the handle."
  218.  
  219.         if flags & MB_NOGRID:
  220.             cleargrid()
  221.         else:
  222.             setupgrid(editor)
  223.         old, ri = self.drag(v1, v2, flags, view)
  224.         if (ri is None) or (len(old)!=len(ri)):
  225.             return
  226.         undo = quarkx.action()
  227.         for i in range(0,len(old)):
  228.             undo.exchange(old[i], ri[i])
  229.         editor.ok(undo, undomsg or self.undomsg)
  230.         if flags & MB_NOGRID:
  231.             setupgrid(editor)
  232.  
  233.     def OriginItems(self, editor, view):
  234.         "Returns a list of menu items to set the coordinates of the handle."
  235.         if view is None:
  236.             return []
  237.         def origin1click(m, self=self, editor=editor, view=view):
  238.             def origin1change(newpos, self=self, editor=editor, view=view):
  239.                 self.Action(editor, self.pos, newpos, MB_NOGRID, view)
  240.             XYZDialog(editor.form, origin1change, self.pos)
  241.         return [qmenu.sep, qmenu.item("C&oordinates...", origin1click, "enter coordinates")]
  242.  
  243.  
  244.  
  245. class Rotate3DHandle(GenericHandle):
  246.     "3D rotating handle, to set a direction."
  247.  
  248.     undomsg = Strings[513]
  249.     hint = "rotate (Ctrl key: force to a common angle)|rotate this"
  250.     # MODE required !
  251.  
  252.     def __init__(self, center, normal, scale1, icon):
  253.         GenericHandle.__init__(self, center + normal*(lengthnormalvect/scale1))
  254.         self.center = center
  255.         self.normal = normal
  256.         self.icon = icon
  257.  
  258.     def draw(self, view, cv, draghandle=None):
  259.         p1, p2 = view.proj(self.center), view.proj(self.pos)
  260.         self.draw1(view, cv.reset(), p1, p2, p1<p2)
  261.  
  262.     def draw1(self, view, cv, p1, p2, fromback):
  263.         if p2.visible:
  264.             if fromback:  # viewing from backwards
  265.                 cv.draw(self.icon, p2.x-8, p2.y-8)
  266.                 cv.line(p1, p2)
  267.             else:            # viewing from forwards or from the side
  268.                 cv.line(p1, p2)
  269.                 cv.draw(self.icon, p2.x-8, p2.y-8)
  270.         else:
  271.             cv.line(p1, p2)
  272.  
  273.     def drawred(self, redimages, view, redcolor, oldnormal=None):
  274.         ##if len(redimages):
  275.             if oldnormal is None:
  276.                 try:
  277.                     oldnormal = self.newnormal
  278.                 except AttributeError:
  279.                     return
  280.                 if oldnormal is None:
  281.                     return
  282.             cv = view.canvas()
  283.             cv.pencolor = redcolor
  284.             cv.line(view.proj(self.center), view.proj(self.center+oldnormal*abs(self.pos-self.center)))
  285.             return oldnormal
  286.  
  287.     def drag(self, v1, v2, flags, view):
  288.         if MapOption("AutoAdjustNormal", self.MODE):
  289.             flags = flags | MB_CTRL
  290.         delta = v2-v1
  291.         new = None
  292.         self.draghint = ""
  293.         av = self.pos + delta - self.center
  294.         if av:
  295.             if flags&MB_CTRL:
  296.                 av = alignanglevect(av, self.MODE)
  297.             av = av.normalized
  298.             if (flags&MB_REDIMAGE) or (av-self.normal):
  299.                 new = av
  300.                 pitch,roll,yaw = vec2angles1(av)
  301.                 self.draghint = "pitch %s    roll %s" % (quarkx.ftos(pitch), quarkx.ftos(roll))
  302.         old, ri, self.newnormal = self.dragop(flags, new)
  303.         return old, ri
  304.  
  305.     def menu(self, editor, view):
  306.  
  307.         def forceangle1click(m, self=self, editor=editor, view=view):
  308.             self.Action(editor, self.pos, self.pos, MB_CTRL, view, Strings[559])
  309.  
  310.         anglestep = quarkx.setupsubset(self.MODE, "Building")["ForceAngleStep"][0]
  311.         return [qmenu.item("&Force to nearest %s deg.\tCtrl" % quarkx.ftos(anglestep), forceangle1click,
  312.           "|This command forces the angle to a 'round' value. It works like a kind of grid for angles.\n\nSet the 'angle grid' in the Configuration box, Map, Building, 'Force to angle'. See also the Options menu, 'Adjust angles automatically'.")]
  313.  
  314.  
  315. def updatecenters(item,delta):
  316.     if item["usercenter"]:
  317.         center = quarkx.vect(item["usercenter"])
  318.         item["usercenter"]=(center+delta).tuple
  319.     for subitem in item.subitems:
  320. #        debug('subitem '+subitem.name)
  321.         updatecenters(subitem,delta)
  322.  
  323. class CenterHandle(GenericHandle):
  324.     "Handle at the center of an object."
  325.  
  326.     hint = "move (Ctrl key: force to grid)|move with the mouse"
  327.     
  328.     def __init__(self, pos, centerof, color=RED, caninvert=0):
  329.         GenericHandle.__init__(self, pos)
  330.         self.centerof = centerof
  331.         self.color = color
  332.         if caninvert:
  333.             self.colormask = WHITE
  334.         else:
  335.             self.colormask = 0
  336.  
  337.     def draw(self, view, cv, draghandle=None):
  338.         p = view.proj(self.pos)
  339.         if p.visible:
  340.             cv.reset()
  341.             # cv.pencolor is either black or white depending on the color layout
  342.             cv.brushcolor = (cv.pencolor & self.colormask) ^ self.color
  343.             cv.rectangle(p.x-3, p.y-3, p.x+4, p.y+4)
  344.  
  345.     def drag(self, v1, v2, flags, view):
  346.         delta = v2-v1
  347.         if flags&MB_CTRL:
  348.             g1 = grid[1]
  349.         else:
  350.             delta = aligntogrid(delta, 0)
  351.             g1 = 0
  352.         self.draghint = vtohint(delta)
  353.         if delta or (flags&MB_REDIMAGE):
  354.             new = self.centerof.copy()
  355.             new.translate(delta, g1)
  356.             updatecenters(new,delta)
  357.             new = [new]
  358.         else:
  359.             new = None
  360.         return [self.centerof], new
  361.  
  362.  
  363. class IconHandle(CenterHandle):
  364.     "Like CenterHandle but displays an icon instead of a square."
  365.  
  366.     def __init__(self, pos, centerof, icon=None):
  367.         CenterHandle.__init__(self, pos, centerof)
  368.  
  369.         if icon is None:
  370.             #
  371.             # Get the default icon for the given entity.
  372.             #
  373.             icon = centerof.geticon(1)
  374.  
  375.         self.icon = icon
  376.         self.size = (8,8)
  377.  
  378.  
  379.     def draw(self, view, cv, draghandle=None):
  380.         p = view.proj(self.pos)
  381.         if p.visible:
  382.             cv.draw(self.icon, p.x-8, p.y-8)
  383.  
  384.  
  385. #
  386. # 3D views "eye" handles.
  387. #
  388.  
  389. class EyePosition(GenericHandle):
  390.  
  391.     hint = "camera for the 3D view||This 'eye' represents the position of the camera of the 3D perspective view. You can use it to quickly move the camera elsewhere.\n\nIf several 3D views are opened, you will see several 'eyes', one for each camera."
  392.  
  393.     def __init__(self, view, view3D):
  394.         pos, roll, pitch = view3D.cameraposition
  395.         GenericHandle.__init__(self, pos)
  396.         self.view3D = view3D
  397.         self.normal = angles2vec1(pitch * rad2deg, roll * rad2deg, 0)
  398.  
  399.     def drag(self, v1, v2, flags, view):
  400.         pack = self.view3D.cameraposition
  401.         if pack is not None:
  402.             pos, roll, pitch = pack
  403.             delta = v2-v1
  404.             if not (flags&MB_CTRL):
  405.                 delta = aligntogrid(delta, 0)
  406.             self.draghint = vtohint(delta)
  407.             pos = self.pos + delta
  408.             if flags&MB_CTRL:
  409.                 pos = aligntogrid(pos, 1)
  410.             self.view3D.animation = flags&MB_DRAGGING
  411.             self.view3D.cameraposition = pos, roll, pitch
  412.             if flags&MB_DRAGGING:
  413.                 self.newpos = pos
  414.                 return [], []
  415.         return None, None
  416.  
  417.     def drawred(self, redimages, view, redcolor, oldpos=None):
  418.         if oldpos is None:
  419.             try:
  420.                 oldpos = self.newpos
  421.             except AttributeError:
  422.                 return
  423.             if oldpos is None:
  424.                 return
  425.         p = view.proj(oldpos)
  426.         if p.visible:
  427.             cv = view.canvas()
  428.             cv.pencolor = redcolor
  429.             cv.line(p.x-3, p.y-3, p.x+4, p.y+4)
  430.             cv.line(p.x-3, p.y+3, p.x+4, p.y-4)
  431.         return oldpos
  432.  
  433.     def draw(self, view, cv, draghandle=None):
  434.         p = view.proj(self.pos)
  435.         if p.visible:
  436.             n = self.normal
  437.             v1 = view.vector("X").normalized * n
  438.             v2 = view.vector("Y").normalized * n
  439.             #print self.normal, v1, v2
  440.             if abs(v1)<0.3 and abs(v2)<0.3:
  441.                 if n*view.vector(self.pos) > 0:
  442.                     icon = 0
  443.                 else:
  444.                     icon = 1
  445.             else:
  446.                 icon = 2 + (quarkx.rnd(math.atan2(v2, v1) * rad2deg / 45) & 7)
  447.             cv.draw(mapicons[icon], p.x-8, p.y-8)
  448.  
  449.  
  450. class EyeDirection(Rotate3DHandle):
  451.  
  452.     hint = "camera direction||This is the direction the 'eye' is looking to. You can use it to quickly rotate the camera with the mouse.\n\nThe 'eye' itself represents the position of the camera of the 3D perspective view. You can use it to quickly move the camera elsewhere.\n\nIf several 3D views are opened, you will see several 'eyes', one for each camera."
  453.     # MODE required !
  454.  
  455.     def __init__(self, view, view3D):
  456.         self.camera = view3D.cameraposition
  457.         forward = angles2vec1(self.camera[2] * rad2deg, self.camera[1] * rad2deg, 0) 
  458.         Rotate3DHandle.__init__(self, self.camera[0], forward, view.scale(), mapicons[12])
  459.         self.view3D = view3D
  460.  
  461.     def dragop(self, flags, av):
  462.         if av is not None:
  463.             self.view3D.animation = flags&MB_DRAGGING
  464.             pos, roll, pitch = self.camera
  465.             pitch, roll, yaw = vec2angles1(av)
  466.             self.view3D.cameraposition = pos, roll * deg2rad, pitch * deg2rad
  467.             if flags&MB_DRAGGING:
  468.                 return [], [], av
  469.         return None, None, av
  470.  
  471.  
  472.  
  473. #
  474. # Linear Mapping Circle handles.
  475. #
  476.  
  477. class LinearHandle(GenericHandle):
  478.     "Linear Box handles."
  479.  
  480.     def __init__(self, pos, mgr):
  481.         GenericHandle.__init__(self, pos)
  482.         self.mgr = mgr    # a LinHandlesManager instance
  483.  
  484.     def drag(self, v1, v2, flags, view):
  485.         delta = v2-v1
  486.         if flags&MB_CTRL:
  487.             g1 = 1
  488.         else:
  489.             delta = aligntogrid(delta, 0)
  490.             g1 = 0
  491.         if delta or (flags&MB_REDIMAGE):
  492.             new = map(lambda obj: obj.copy(), self.mgr.list)
  493.             if not self.linoperation(new, delta, g1, view):          
  494.                 if not flags&MB_REDIMAGE:
  495.                     new = None
  496.         else:
  497.             new = None
  498.         return self.mgr.list, new
  499.  
  500.     def linoperation(self, list, delta, g1, view):
  501.         matrix = self.buildmatrix(delta, g1, view)
  502.         if matrix is None: return
  503.         for obj in list:
  504.             obj.linear(self.center, matrix)
  505.         return 1
  506.  
  507.  
  508. class LinRedHandle(LinearHandle):
  509.     "Linear Box: Red handle at the center."
  510.  
  511.     hint = "move selection (Ctrl key: force to grid)|move selection"
  512.  
  513.     def __init__(self, pos, mgr):
  514.         LinearHandle.__init__(self, pos, mgr)
  515.         self.cursor = CR_MULTIDRAG
  516.  
  517.     def draw(self, view, cv, draghandle=None):
  518.         p = view.proj(self.pos)
  519.         if p.visible:
  520.             cv.reset()
  521.             cv.brushcolor = self.mgr.color
  522.             cv.rectangle(p.x-3, p.y-3, p.x+4, p.y+4)
  523.  
  524.     def linoperation(self, list, delta, g1, view):
  525.         for obj in list:
  526.             obj.translate(delta, g1 and grid[0])
  527.         self.draghint = vtohint(delta)
  528.         return delta
  529.  
  530.  
  531. class LinSideHandle(LinearHandle):
  532.     "Linear Box: Red handle at the side for distortion/shearing."
  533.  
  534.     undomsg = Strings[527]
  535.     hint = "enlarge / shear (Ctrl key: either enlarge or shear)||This handle lets you distort the selected object(s).\n\nIf you move the handle in the direction of or away from the center, you will shrink or enlarge the objects correspondingly. If you move the handle in the other direction, you will 'shear' the objects. Hold down the Ctrl key to prevents the objects from being enlarged and sheared at the same time."
  536.  
  537.     def __init__(self, center, side, dir, mgr, firstone):
  538.         pos1 = quarkx.vect(center.tuple[:dir] + (side.tuple[dir],) + center.tuple[dir+1:])
  539.         LinearHandle.__init__(self, pos1, mgr)
  540.         self.center = center - (pos1-center)
  541.         self.dir = dir
  542.         self.firstone = firstone
  543.         self.inverse = side.tuple[dir] < center.tuple[dir]
  544.         self.cursor = CR_LINEARV
  545.  
  546.     def draw(self, view, cv, draghandle=None):
  547.         if self.firstone:
  548.             self.mgr.drawbox(view)   # draw the full box
  549.         p = view.proj(self.pos)
  550.         if p.visible:
  551.             cv.reset()
  552.             cv.brushcolor = self.mgr.color
  553.             cv.rectangle(p.x-2.5, p.y-2.5, p.x+3.5, p.y+3.5)
  554.  
  555.     def buildmatrix(self, delta, g1, view):
  556.         npos = self.pos+delta
  557.         if g1:
  558.              npos = aligntogrid(npos, 1)
  559.         normal = view.vector(self.pos).normalized
  560.         dir = self.dir
  561.         v = (npos - self.center) / abs(self.pos - self.center)
  562.         if self.inverse:
  563.             v = -v
  564.         m = [quarkx.vect(1,0,0), quarkx.vect(0,1,0), quarkx.vect(0,0,1)]
  565.         if g1:
  566.             w = list(v.tuple)
  567.             x = w[dir]-1
  568.             if x*x > w[dir-1]*w[dir-1] + w[dir-2]*w[dir-2]:
  569.                 w[dir-1] = w[dir-2] = 0   # force distortion in this single direction
  570.             else:
  571.                 w[dir] = 1                # force pure shearing
  572.             v = quarkx.vect(tuple(w))
  573.         else:
  574.             w = v.tuple
  575.         self.draghint = "enlarge %d %%   shear %d deg." % (100.0*w[dir], math.atan2(math.sqrt(w[dir-1]*w[dir-1] + w[dir-2]*w[dir-2]), w[dir])*180.0/math.pi)
  576.         m[dir] = v
  577.         return quarkx.matrix(tuple(m))
  578.  
  579.        # v = self.pos - self.center
  580.        # diff = v * (npos - self.center) / (v.x*v.x+v.y*v.y+v.z*v.z)
  581.        # if abs(diff-1) < epsilon: return
  582.        # dir = self.dir
  583.        # return quarkx.matrix(
  584.        #   (dir!=0 or diff, 0, 0),
  585.        #   (0, dir!=1 or diff, 0),
  586.        #   (0, 0, dir!=2 or diff))
  587.  
  588.  
  589. class LinCornerHandle(LinearHandle):
  590.     "Linear Box: Red handle at the corner for rotation/zooming."
  591.  
  592.     undomsg = Strings[528]
  593.     hint = "zoom / rotate (Ctrl key: either zoom or rotate)||This handle lets you rotate and scale the selected object(s).\n\nIf you move the handle in the direction of or away from the center, you will zoom the objects and make them smaller or larger. If you move the handle around the center, the objects rotate. Hold down the Ctrl key to prevent zooming and rotation to occur simultaneously."
  594.  
  595.     def __init__(self, center, pos1, mgr, realpoint=None):
  596.         LinearHandle.__init__(self, pos1, mgr)
  597.         if realpoint is None:
  598.             self.pos0 = pos1
  599.         else:
  600.             self.pos0 = realpoint
  601.         self.center = center - (pos1-center)
  602.         self.cursor = CR_CROSSH
  603.  
  604.     def draw(self, view, cv, draghandle=None):
  605.         p = view.proj(self.pos)
  606.         if p.visible:
  607.             cv.reset()
  608.             cv.brushcolor = self.mgr.color
  609.             cv.polygon([(p.x-3,p.y), (p.x,p.y-3), (p.x+3,p.y), (p.x,p.y+3)])
  610.  
  611.     def buildmatrix(self, delta, g1, view):
  612.         normal = view.vector(self.pos).normalized
  613.         texp4 = self.pos-self.center
  614.         texp4 = texp4 - normal*(normal*texp4)
  615.         npos = self.pos + delta
  616.         if g1:
  617.             npos = aligntogrid(npos, 1)
  618.         npos = npos-self.center
  619.         npos = npos - normal*(normal*npos)
  620.         m = diff = None
  621.         if g1 and npos:
  622.             v = npos.normalized * abs(texp4)
  623.             if abs(v-texp4) > abs(v-npos):
  624.                 diff = 1.0  # force pure rotation
  625.             else:
  626.                 m = quarkx.matrix((1,0,0),(0,1,0),(0,0,1))    # force pure zooming
  627.         if m is None:
  628.             m = UserRotationMatrix(normal, npos, texp4, 0)
  629.             if m is None: return
  630.         if diff is None: diff = abs(npos) / abs(texp4)
  631.         self.draghint = "rotate %d deg.   zoom %d %%" % (math.acos(m[0,0])*180.0/math.pi, 100.0*diff)
  632.         return m * diff
  633.  
  634.  
  635.  
  636. def AutoScrollTimer(info):
  637.     sender, x, y = info
  638.     view = sender.view
  639.     try:
  640.         layout = view.owner.info.layout
  641.     except:
  642.         return
  643.     hbar, vbar = view.scrollbars
  644.     MakeScroller(layout, view)(hbar[0] + x, vbar[0] + y)
  645.     sender.x0 = sender.x0 - x
  646.     sender.y0 = sender.y0 - y
  647.     return 80
  648.  
  649.  
  650.  
  651. #
  652. # Setup Handle hints.
  653. #
  654.  
  655. def FilterHandles(handlelist, mode):
  656.     if not MapOption("HandleHints", mode):
  657.         for handle in handlelist:
  658.             try:
  659.                 if handle.hint[0] != "?":
  660.                     handle.hint = ""
  661.             except:
  662.                 pass
  663.     return handlelist
  664.  
  665.  
  666.  
  667. #
  668. # Function that computes a rotation matrix out of a mouse movement.
  669. #
  670.  
  671. def UserRotationMatrix(normal, texpdest, texp4, g1):
  672.      # normal: normal vector for the view plane
  673.      # texpdest: new position of the reference vector texp4
  674.      # texp4: reference vector (handle position minus rotation center)
  675.      # g1: if True, snap angle to grid
  676.     SNAP = 0.998
  677.     if not texp4: return
  678.     v3 = normal
  679.     norme1 = abs(texp4)
  680.     if not texpdest: return
  681.     norme2 = abs(texpdest)
  682.     sinangle = (v3*(texp4^texpdest)) / (norme1*norme2)
  683.     norme1 = sinangle*sinangle
  684.     if norme1 > SNAP:
  685.         if sinangle>0:
  686.             sinangle=1
  687.         else:
  688.             sinangle=-1
  689.         cosangle=0
  690.     else:
  691.         cosangle=1-norme1
  692.         if cosangle > SNAP:
  693.             sinangle, cosangle = 0, 1
  694.         else:
  695.             cosangle = math.sqrt(cosangle)
  696.         if texpdest * texp4 < 0:
  697.             cosangle = -cosangle
  698.         if cosangle == 1:
  699.             return
  700.     if g1:
  701.         if abs(cosangle)>abs(sinangle):
  702.             sinangle = 0
  703.             cosangle = (-1,1)[cosangle>0]
  704.         else:
  705.             cosangle = 0
  706.             sinangle = (-1,1)[sinangle>0]
  707.     m = quarkx.matrix((cosangle,  sinangle, 0),
  708.                       (-sinangle, cosangle, 0),
  709.                       (    0,        0,     1))
  710.     v = orthogonalvect(normal, None)
  711.     base = quarkx.matrix(v, v^normal, -normal)
  712.     return base * m * (~base)
  713.  
  714.  
  715. #
  716. # Drag Objects are created when a mouse drag begins and
  717. # destroyed when it ends. See MapEditor.dragobject.
  718. #
  719. # The class DragObject is only a base class; handle drags
  720. # are handled by the HandleDragObject class.
  721. #
  722.  
  723. class DragObject:
  724.     "Stores information about the current mouse drag."
  725.  
  726.     redimages = None
  727.     handle = None
  728.     hint = None
  729.  
  730.     def __init__(self, view, x, y, z):
  731.         self.view = view
  732.         self.x0 = x
  733.         self.y0 = y
  734.         self.z0 = z
  735.         self.pt0 = view.space(x, y, z)
  736.         self.scrolltimer = None
  737.  
  738.     def dragto(self, x, y, flags):
  739.         "Called by the map editor when the mouse moves."
  740.         pass   # abstract
  741.  
  742.     def ok(self, editor, x, y, flags):
  743.         "Called when the drag ends."
  744.         pass   # abstract
  745.  
  746.     def drawredimages(self, view):
  747.         pass   # abstract
  748.  
  749.     def backup(self):
  750.         return None, None
  751.  
  752.     def autoscroll_stop(self):
  753.         if self.scrolltimer is not None:
  754.             quarkx.settimer(AutoScrollTimer, self.scrolltimer, 0)
  755.             self.scrolltimer = None
  756.  
  757.     def autoscroll(self, x, y):
  758.         if self.view.info["type"] == "3D":
  759.             return
  760.         w,h = self.view.clientarea
  761.         if x>=0 and y>=0 and x<w and y<h:
  762.             self.autoscroll_stop()
  763.             return
  764.         if x<0: x=-32
  765.         elif x>=w: x=32
  766.         else: x=0
  767.         if y<0: y=-32
  768.         elif y>=h: y=32
  769.         else: y=0
  770.         timer = (self, x, y)
  771.         if timer != self.scrolltimer:
  772.             quarkx.settimer(AutoScrollTimer, self.scrolltimer, 0)
  773.             self.scrolltimer = timer
  774.             quarkx.settimer(AutoScrollTimer, self.scrolltimer, 80)
  775.  
  776.  
  777. #
  778. # RedImageDragObject is an abstract class that can display red
  779. # wireframe images while the user drags the mouse. Subclasses
  780. # of this are HandleDragObject, which draws the map objects
  781. # while they are distorted, and RectangleDragObject, which
  782. # displays a red rectangle.
  783. #
  784.  
  785.  
  786. class RedImageDragObject(DragObject):
  787.     "Dragging that draws a red wireframe image of something."
  788.  
  789.     def __init__(self, view, x, y, z, redcolor):
  790.         DragObject.__init__(self, view, x, y, z)
  791.         self.redcolor = redcolor
  792.         self.redhandledata = None
  793.  
  794.     def buildredimages(self, x, y, flags):
  795.         return None, None   # abstract
  796.  
  797.     def ricmd(self):
  798.         return None, refreshtimer     # default behaviour
  799.  
  800.     def dragto(self, x, y, flags):
  801.         if flags&MB_DRAGGING:
  802.             self.autoscroll(x,y)
  803.         old, ri = self.buildredimages(x, y, flags)
  804.         self.drawredimages(self.view, 1)
  805.         self.redimages = ri
  806.         if flags&MB_DRAGGING:
  807.             self.drawredimages(self.view, 2)
  808.         return old
  809.  
  810.     def drawredimages(self, view, internal=0):
  811.         if self.redimages is not None:
  812.             mode = DM_OTHERCOLOR|DM_BBOX
  813.             special, refresh = self.ricmd()
  814.             if special is None:    # can draw a red image only
  815.                 if internal==1:    # erase the previous image
  816.                     for r in self.redimages:
  817.                         view.drawmap(r, mode)
  818.                     if self.redhandledata is not None:
  819.                         self.handle.drawred(self.redimages, view, view.color, self.redhandledata)
  820.                 else:
  821.                     for r in self.redimages:
  822.                         view.drawmap(r, mode, self.redcolor)
  823.                     if self.handle is not None:
  824.                         self.redhandledata = self.handle.drawred(self.redimages, view, self.redcolor)
  825.             else:   # must redraw everything
  826.                 if internal==2:
  827.                     view.invalidate()
  828.             if internal==2:    # set up a timer to update the other views as well
  829.                 quarkx.settimer(refresh, self, 150)
  830.  
  831.     def backup(self):
  832.         special, refresh = self.ricmd()
  833.         if (special is None) or (self.redimages is None):
  834.             return None, None
  835.         backup = special.copy()
  836.         special.copyalldata(self.redimages[0])
  837.         return special, backup
  838.  
  839.     def ok(self, editor, x, y, flags):   # default behaviour is to create an object out of the red image 
  840.         self.autoscroll_stop()
  841.         old = self.dragto(x, y, flags)
  842.         if (self.redimages is None) or (len(old)!=len(self.redimages)):
  843.             self.view.invalidate()
  844.             editor.invalidateviews()
  845.             return
  846.         undo = quarkx.action()
  847.         for i in range(0,len(old)):
  848.             undo.exchange(old[i], self.redimages[i])
  849.         self.handle.ok(editor, undo, old, self.redimages)
  850.  
  851.  
  852. class HandleDragObject(RedImageDragObject):
  853.  
  854.     def __init__(self, view, x, y, handle, redcolor):
  855.         RedImageDragObject.__init__(self, view, x, y, view.proj(handle.pos).z, redcolor)
  856.         self.pt0 = handle.pos
  857.         self.handle = handle
  858.         handle.start_drag(view, x, y)
  859.  
  860.     def buildredimages(self, x, y, flags):
  861.         pt1 = self.view.space(x, y, self.z0)
  862.         result = self.handle.drag(self.pt0, pt1, flags, self.view)
  863.         try:
  864.             self.hint = self.handle.draghint
  865.         except:
  866.             pass
  867.         return result
  868.  
  869.     def ricmd(self):
  870.         return self.handle.getdrawmap()
  871.  
  872.  
  873.  
  874. def refreshtimer(self):
  875.     for v in self.views:
  876.         v.invalidate()
  877.  
  878. def refreshtimertex(self):
  879.     for v in self.views:
  880.         if (v.viewmode in texturedmodes) and (v is not self.view):
  881.             v.invalidate(1)
  882.  
  883.  
  884. #
  885. # Free Zoom in/out following the mouse move.
  886. #
  887.  
  888. class FreeZoomDragObject(DragObject):
  889.  
  890.     BaseSensitivity = 0.007
  891.     AbsoluteMinimum = 0.01
  892.     AbsoluteMaximum = 100.0
  893.     InfiniteMouse   = 1
  894.     # MODE required !
  895.  
  896.     def __init__(self, viewlist, view, x, y):
  897.         self.x0 = x
  898.         self.y0 = y
  899.         self.scale0 = view.info["scale"]
  900.         if view.info.has_key("custom"):
  901.             self.viewlist = [view]
  902.         else:
  903.             self.viewlist = viewlist
  904.         self.view = view
  905.  
  906.     def dragto(self, x, y, flags):
  907.           # moving the mouse RIGHT means zoom IN
  908.           # moving the mouse DOWN also means zoom IN
  909.           # if you are unhappy with this, change it here...
  910.  
  911.           # or set a negative value in the Configuration dialog for this
  912.         sensitivity, = quarkx.setupsubset(self.MODE, "Display")["FreeZoom"]
  913.  
  914.         scale = self.scale0 * math.exp((x-self.x0+y-self.y0) * sensitivity * self.BaseSensitivity)
  915.         if scale<self.AbsoluteMinimum: scale=self.AbsoluteMinimum
  916.         elif scale>self.AbsoluteMaximum: scale=self.AbsoluteMaximum
  917.         setviews(self.viewlist, "scale", scale)
  918.         self.view.repaint()
  919.  
  920.  
  921.  
  922. #
  923. # Scroll the view while the mouse moves.
  924. #
  925.  
  926. def MakeScroller(layout, view):
  927.     sbviews = [None, None]
  928.     for ifrom, linkfrom, ito, linkto in layout.sblinks:
  929.         if linkto is view:
  930.             sbviews[ito] = (ifrom, linkfrom)
  931.     def scroller(x, y, view=view, hlink=sbviews[0], vlink=sbviews[1]):
  932.         view.scrollto(x, y)
  933.         if hlink is not None:
  934.             if hlink[0]:
  935.                 hlink[1].scrollto(None, x)
  936.             else:
  937.                 hlink[1].scrollto(x, None)
  938.         if vlink is not None:
  939.             if vlink[0]:
  940.                 vlink[1].scrollto(None, y)
  941.             else:
  942.                 vlink[1].scrollto(y, None)
  943.         view.update()
  944.     return scroller
  945.  
  946.  
  947. class ScrollViewDragObject(DragObject):
  948.  
  949.     InfiniteMouse = 1
  950.  
  951.     def __init__(self, editor, view, x, y):
  952.         hbar, vbar = view.scrollbars
  953.         self.view = view
  954.         self.x0 = hbar[0] + x
  955.         self.y0 = vbar[0] + y
  956.         self.scroller = MakeScroller(editor.layout, view)
  957.  
  958.     def dragto(self, x, y, flags):
  959.         x = self.x0-x
  960.         y = self.y0-y
  961.         self.scroller(x, y)
  962.  
  963.  
  964. #
  965. # Mouse Free View like in Quake.
  966. #
  967.  
  968. class AnimatedDragObject(DragObject):
  969.     def ok(self, editor, x, y, flags):
  970.         self.view.animation = 0
  971.  
  972.  
  973. class FreeViewDragObject(AnimatedDragObject):
  974.  
  975.     InfiniteMouse = 1
  976.  
  977.     def __init__(self, editor, view, x, y):
  978.         self.view = view
  979.         self.x0 = x
  980.         self.y0 = y
  981.         self.pos0, self.roll0, self.pitch0 = self.view.cameraposition
  982.         #
  983.         # Read sensitivity (neg. numbers invert movements).
  984.         #
  985.         setup = quarkx.setupsubset(SS_GENERAL, "3D view")
  986.         self.f0 = setup["MouseHLook"][0] * 0.0006, setup["MouseVLook"][0] * 0.0006
  987.  
  988.     def dragto(self, x, y, flags):
  989.         x = self.x0-x
  990.         y = self.y0-y
  991.         fx, fy = self.f0
  992.         roll = self.roll0 + x*fx
  993.         pitch = self.pitch0 + y*fy
  994.         if pitch<-1.5: pitch = -1.5
  995.         elif pitch>1.5: pitch = 1.5
  996.  
  997.         self.view.animation = 1
  998.         self.view.cameraposition = self.pos0, roll, pitch
  999.  
  1000.  
  1001. #
  1002. # Mouse Walk like in Quake.
  1003. #
  1004.  
  1005. class WalkDragObject(AnimatedDragObject):
  1006.  
  1007.     InfiniteMouse = 1
  1008.  
  1009.     def __init__(self, editor, view, x, y):
  1010.         self.view = view
  1011.         self.x0 = x
  1012.         self.y0 = y
  1013.         self.pos0, self.roll0, self.pitch0 = self.view.cameraposition
  1014.         #
  1015.         # Read sensitivity (neg. numbers invert movements).
  1016.         #
  1017.         setup = quarkx.setupsubset(SS_GENERAL, "3D view")
  1018.         self.f0 = setup["MouseHLook"][0] * 0.0006, setup["MouseWalk"][0] * 0.32
  1019.  
  1020.     def dragto(self, x, y, flags):
  1021.         x = self.x0-x
  1022.         y, self.y0 = self.y0-y, y
  1023.         fx, fy = self.f0
  1024.         roll = self.roll0 + x*fx
  1025.         forward = angles2vec1(self.pitch0*rad2deg, roll*rad2deg, 0)
  1026.         pos = self.pos0 + forward*y * fy
  1027.  
  1028.         self.view.animation = 1
  1029.         self.view.cameraposition = pos, roll, self.pitch0
  1030.         self.pos0 = pos
  1031.  
  1032.  
  1033. #
  1034. # Mouse SideStep walk (left-right-up-down).
  1035. #
  1036.  
  1037. class SideStepDragObject(AnimatedDragObject):
  1038.  
  1039.     InfiniteMouse = 1
  1040.  
  1041.     def __init__(self, editor, view, x, y):
  1042.         self.view = view
  1043.         self.x0 = x
  1044.         self.y0 = y
  1045.         self.camerapos0 = self.view.cameraposition
  1046.         forward = angles2vec1(self.camerapos0[2]*rad2deg, self.camerapos0[1]*rad2deg, 0)
  1047.         left = orthogonalvect(forward, editor.layout.views[0])
  1048.         #
  1049.         # Read sensitivity (neg. numbers invert movements).
  1050.         #
  1051.         setup = quarkx.setupsubset(SS_GENERAL, "3D view")
  1052.         self.vleft = left * setup["MouseSideStep"][0] * 0.12
  1053.         self.vtop = forward^left * setup["MouseUpDown"][0] * 0.12
  1054.  
  1055.     def dragto(self, x, y, flags):
  1056.         pos, roll, pitch = self.camerapos0
  1057.         x = self.x0-x
  1058.         y = self.y0-y
  1059.  
  1060.         self.view.animation = 1
  1061.         self.view.cameraposition = pos + self.vleft*x + self.vtop*y, roll, pitch
  1062.  
  1063. #
  1064. # circlestafe utilities
  1065. #
  1066. def vec2rads(v):
  1067.     "returns pitch, yaw, in radians"
  1068.     v = v.normalized
  1069.     import math
  1070.     pitch = -math.sin(v.z)
  1071.     yaw = math.atan2(v.y, v.x)
  1072.     return pitch, yaw
  1073.  
  1074.  
  1075. class CircleStrafeDragObject(SideStepDragObject):
  1076.  
  1077.     def __init__(self, editor, view, x, y):
  1078.         self.editor=editor
  1079.         SideStepDragObject.__init__(self, editor, view, x, y)
  1080.         
  1081.     def dragto(self, x, y, flags):
  1082.         sel = self.editor.layout.explorer.sellist
  1083.         if sel:
  1084.             min, max = quarkx.boundingboxof(sel)
  1085.             center = .5*(max+min)
  1086.             pos, yaw, pitch = self.camerapos0
  1087.             dist = abs(pos-center)
  1088.             x = self.x0-x
  1089.             y = self.y0-y
  1090.             newdir = (pos + self.vleft*x + self.vtop*y - center).normalized
  1091.             newpos = center+dist*newdir
  1092.             pitch, yaw = vec2rads(-newdir)
  1093.             self.view.animation = 1
  1094.             self.view.cameraposition = newpos, yaw, pitch
  1095.         else:
  1096.             SideStepDragObject.dragto(self, x, y, flags)
  1097.     
  1098.  
  1099. #
  1100. # Displays a red rectangle created by the mouse movement.
  1101. # This is a base class for classes that do something with
  1102. # the rectangle, e.g. select all polyhedron within it, or
  1103. # zoom in or out.
  1104. #
  1105.  
  1106. class RectangleDragObject(RedImageDragObject):
  1107.  
  1108.     def __init__(self, view, x, y, redcolor, todo):
  1109.         RedImageDragObject.__init__(self, view, x, y, view.depth[0], redcolor)
  1110.         self.todo = todo
  1111.  
  1112.     def buildredimages(self, x, y, flags, depth=None):
  1113.         if x==self.x0 or y==self.y0:
  1114.             return None, None
  1115.         if depth is None:
  1116.             min, max = self.view.depth
  1117.             max = max - 0.0001
  1118.         else:
  1119.             min, max = depth
  1120.         pts = [self.view.space(self.x0, self.y0, min),
  1121.                self.view.space(x, self.y0, min),
  1122.                self.view.space(x, y, min),
  1123.                self.view.space(self.x0, y, min)]
  1124.         pts.append(pts[0])
  1125.         pts2 = [self.view.space(self.x0, self.y0, max),
  1126.                 self.view.space(x, self.y0, max),
  1127.                 self.view.space(x, y, max),
  1128.                 self.view.space(self.x0, y, max)]
  1129.         if (x<self.x0)^(y<self.y0):
  1130.             pts.reverse()
  1131.             pts2.reverse()
  1132.         poly = quarkx.newobj("redbox:p")
  1133.         for i in (0,1,2,3):
  1134.             face = quarkx.newobj("side:f")
  1135.             face.setthreepoints((pts[i], pts[i+1], pts2[i]), 0)
  1136.             poly.appenditem(face)
  1137.         face = quarkx.newobj("front:f")
  1138.         face.setthreepoints((pts[0], pts[3], pts[1]), 0)
  1139.         poly.appenditem(face)
  1140.         face = quarkx.newobj("back:f")
  1141.         face.setthreepoints((pts2[0], pts2[1], pts2[3]), 0)
  1142.         poly.appenditem(face)
  1143.         if self.view.info["type"] == "3D":
  1144.             for f in poly.subitems:
  1145.                 f.swapsides()
  1146.         if poly.rebuildall() != (0,0):
  1147.             return None, None
  1148.         return None, [poly]
  1149.  
  1150.     def ok(self, editor, x, y, flags):
  1151.         self.autoscroll_stop()
  1152.         self.dragto(x, y, flags)
  1153.         if self.redimages is not None:
  1154.             self.rectanglesel(editor, x,y, self.redimages[0])
  1155.         self.view.invalidate()
  1156.         editor.invalidateviews()
  1157.  
  1158.     def rectanglesel(self, editor, x,y, rectangle):
  1159.         "Called when the drag is over."
  1160.         pass   # abstract
  1161.  
  1162.  
  1163.  
  1164. #
  1165. # Class RectZoomDragObject:
  1166. # Zoom in or out of a rectangle drawn by the mouse movement.
  1167. #
  1168.  
  1169. def ZoomView(editor, view, zoom, clickpt):
  1170.     center = clickpt + (view.screencenter-clickpt)/zoom
  1171.     if view.info.has_key("custom"):
  1172.         setviews([view], "scale", view.info["scale"]*zoom)
  1173.         view.screencenter = center
  1174.     else:
  1175.         editor.setscaleandcenter(view.info["scale"]*zoom, center)
  1176.  
  1177. class RectZoomDragObject(RectangleDragObject):
  1178.     def rectanglesel(self, editor, x,y, rectangle):
  1179.         view = self.view
  1180.         w,h = view.clientarea
  1181.         zoom = min((w/abs(x-self.x0), h/abs(y-self.y0)))
  1182.         if "-" in self.todo:
  1183.             zoom = 1.0/zoom
  1184.         ZoomView(editor, view, zoom, view.space((self.x0+x)*0.5, (self.y0+y)*0.5, view.screencenter.z))
  1185.  
  1186.  
  1187. #
  1188. # Class Rotator2D: a DragObject to rotate flat 3D views with the mouse.
  1189. #
  1190.  
  1191. class Rotator2D(DragObject):
  1192.  
  1193.     InfiniteMouse = 1
  1194.  
  1195.     def __init__(self, view, x, y, redcolor=None, todo=None):
  1196.         info = view.info
  1197.         center = info["center"]
  1198.         DragObject.__init__(self, view, x, y, view.proj(center).z)
  1199.         self.data = info, info["angle"], info["vangle"], info["sfx"]
  1200.  
  1201.     def dragto(self, x, y, flags):
  1202.         info, angle, vangle, scroll = self.data
  1203.         #
  1204.         # Rotate the view. You can adjust the sensibility below.
  1205.         #
  1206.         info["angle"] = angle + (x-self.x0)*0.02
  1207.         vangle = vangle + (y-self.y0)*0.01
  1208.         if vangle<-1.25:
  1209.             vangle = -1.25
  1210.         elif vangle>1.25:
  1211.             vangle = 1.25
  1212.         info["vangle"] = vangle
  1213.  
  1214.         center = self.view.screencenter
  1215.         fixpt = center + self.view.vector(center).normalized * scroll
  1216.  
  1217.         setprojmode(self.view)
  1218.         self.view.screencenter = fixpt - self.view.vector(fixpt).normalized * scroll
  1219.         self.view.repaint()
  1220.  
  1221.     def ok(self, editor, x, y, flags):
  1222.         info = self.view.info
  1223.         info["angle"] = info["angle"] % pi2
  1224.  
  1225. #
  1226. # Function to build a DragObject in response to a MB_STARTDRAG.
  1227. #
  1228.  
  1229. def MouseDragging(editor, view, x, y, s, handle, redcolor):
  1230.     "Called when the user drags the mouse on a map view."
  1231.  
  1232.     
  1233.     if handle is None:
  1234.         if getAttr(editor,'frozenselection') is not None:
  1235.             if editor.layout.explorer.uniquesel is not None:
  1236.                 if "S" in s and "R" in s:
  1237.                     return editor.FrozenDragObject(view,x,y,s,redcolor)
  1238.         
  1239.         if "C" in s:
  1240.             if view.info["type"]=="3D":
  1241.                 return CircleStrafeDragObject(editor, view, x, y) 
  1242.             else:
  1243.                 return SideStepDragObject(editor, view, x, y)           
  1244.         elif "R" in s:     # display a rectangle
  1245.             if ("+" in s) or ("-" in s):
  1246.                 if view.info["type"]=="3D":
  1247.                     return SideStepDragObject(editor, view, x, y)
  1248.                 else:
  1249.                     rectselclass = RectZoomDragObject
  1250.             elif view.info.has_key("mousemode"):
  1251.                 rectselclass = view.info["mousemode"]
  1252.             else:
  1253.                 rectselclass = editor.MouseDragMode
  1254.             if rectselclass is None:
  1255.                 return
  1256.             return rectselclass(view, x, y, redcolor, s)
  1257.         elif "Z" in s:   # free zoom
  1258.             if view.info["type"]=="3D":    # on 3D views, make the camera move and rotate, like in Quake.
  1259.                 return WalkDragObject(editor, view, x, y)
  1260.             dragobj = FreeZoomDragObject(editor.layout.baseviews, view, x, y)
  1261.             dragobj.MODE = editor.MODE
  1262.             return dragobj
  1263.         elif "S" in s:   # scroll
  1264.             if view.info["type"]=="3D":    # on 3D views, make the camera rotate, like in Quake.
  1265.                 return FreeViewDragObject(editor, view, x, y)
  1266.             return ScrollViewDragObject(editor, view, x, y)
  1267.     else:
  1268.         return HandleDragObject(view, x, y, handle, redcolor)
  1269.  
  1270.  
  1271.  
  1272. #
  1273. # Function called in answer to simple clicks (not drags).
  1274. #
  1275.  
  1276. def MouseClicked(editor, view, x, y, s, handle):
  1277.     "Called when the user clicks on a map view."
  1278.  
  1279.     if "M" in s:
  1280.         if handle is not None:   # menu on a handle
  1281.             menu = handle.menu(editor, view)
  1282.             if menu is not None:
  1283.                 view.popupmenu(menu, x,y)
  1284.             return ""
  1285.         flags = "M"    # default menu
  1286.     else:
  1287.         flags = ""
  1288.     if "S" in s:    # if request to select an object
  1289.         if handle is not None:
  1290.             result = handle.click(editor)
  1291.             if result is not None:
  1292.                 return flags+result
  1293.         if view.info.has_key("noclick"):
  1294.             return flags
  1295.         if not "M" in s:
  1296.             for h in view.handles:
  1297.                 h.leave(editor)
  1298.         #
  1299.         #  if selection is frozen, it can't be changed,
  1300.         #    a different handle of the selected object
  1301.         #    can be manipulated
  1302.         #
  1303.         return flags+"1"
  1304.             
  1305.     if view.info["type"]=="3D":
  1306.         #
  1307.         # Zoom in and out on 3D views -- make the camera walk forward or backward.
  1308.         #
  1309.         if "+" in s:       # zoom in (forward)
  1310.             step = STEP3DVIEW
  1311.         elif "-" in s:     # zoom out (backward)
  1312.             step = -STEP3DVIEW
  1313.         else:
  1314.             return flags
  1315.         pos, roll, pitch = view.cameraposition
  1316.         forward = angles2vec1(pitch*rad2deg, roll*rad2deg, 0)
  1317.         pos = pos + forward*step
  1318.         view.cameraposition = pos, roll, pitch
  1319.         return ""
  1320.     #
  1321.     # Zoom in and out on 2D views.
  1322.     #
  1323.     if "+" in s:       # zoom in
  1324.         zoom = MOUSEZOOMFACTOR
  1325.     elif "-" in s:     # zoom out
  1326.         zoom = 1.0/MOUSEZOOMFACTOR
  1327.     else:
  1328.         return flags
  1329.     ZoomView(editor, view, zoom, view.space(x,y,view.screencenter.z))
  1330.     return ""
  1331.  
  1332.  
  1333. #
  1334. # Class that manages the linear box... er... circle.
  1335. #
  1336.  
  1337. class LinHandlesManager:
  1338.     "Linear Box manager."
  1339.  
  1340.     def __init__(self, color, bbox, list):
  1341.         self.color = color
  1342.         bmin, bmax = bbox
  1343.         bmin1 = bmax1 = ()
  1344.         for dir in "xyz":
  1345.             cmin = getattr(bmin, dir)
  1346.             cmax = getattr(bmax, dir)
  1347.             diff = cmax-cmin
  1348.             if diff<32:
  1349.                 diff = 0.5*(32-diff)
  1350.                 cmin = cmin - diff
  1351.                 cmax = cmax + diff
  1352.             bmin1 = bmin1 + (cmin,)
  1353.             bmax1 = bmax1 + (cmax,)
  1354.         self.bmin = quarkx.vect(bmin1)
  1355.         self.bmax = quarkx.vect(bmax1)
  1356.         self.list = list
  1357.  
  1358.     def BuildHandles(self, center=None, minimal=None):
  1359.         "Build a list of handles to put around the box for linear distortion."
  1360.  
  1361.         if center is None:
  1362.             center = 0.5 * (self.bmin + self.bmax)
  1363.         self.center = center
  1364.         if minimal is not None:
  1365.             view, grid = minimal
  1366.             closeto = view.space(view.proj(center) + quarkx.vect(-99,-99,0))
  1367.             distmin = 1E99
  1368.             mX, mY, mZ = self.bmin.tuple
  1369.             X, Y, Z = self.bmax.tuple
  1370.             for x in (X,mX):
  1371.                 for y in (Y,mY):
  1372.                     for z in (Z,mZ):
  1373.                         ptest = quarkx.vect(x,y,z)
  1374.                         dist = abs(ptest-closeto)
  1375.                         if dist<distmin:
  1376.                             distmin = dist
  1377.                             pmin = ptest
  1378.             f = -grid * view.scale(pmin)
  1379.             return [LinCornerHandle(self.center, view.space(view.proj(pmin) + quarkx.vect(f, f, 0)), self, pmin)]
  1380.         h = []
  1381.         for side in (self.bmin, self.bmax):
  1382.             for dir in (0,1,2):
  1383.                 h.append(LinSideHandle(self.center, side, dir, self, not len(h)))
  1384.         mX, mY, mZ = self.bmin.tuple
  1385.         X, Y, Z = self.bmax.tuple
  1386.         for x in (X,mX):
  1387.             for y in (Y,mY):
  1388.                 for z in (Z,mZ):
  1389.                     h.append(LinCornerHandle(self.center, quarkx.vect(x,y,z), self))
  1390.         return h + [LinRedHandle(self.center, self)]
  1391.  
  1392.     def drawbox(self, view):
  1393.         "Draws the circle around all objects."
  1394.  
  1395.         cx, cy = [], []
  1396.         mX, mY, mZ = self.bmin.tuple
  1397.         X, Y, Z = self.bmax.tuple
  1398.         for x in (X,mX):
  1399.             for y in (Y,mY):
  1400.                 for z in (Z,mZ):
  1401.                     p = view.proj(x,y,z)
  1402.                     if not p.visible: return
  1403.                     cx.append(p.x)
  1404.                     cy.append(p.y)
  1405.         mX = min(cx)
  1406.         mY = min(cy)
  1407.         X = max(cx)
  1408.         Y = max(cy)
  1409.         cx = (X+mX)*0.5
  1410.         cy = (Y+mY)*0.5
  1411.         dx = X-cx
  1412.         dy = Y-cy
  1413.         radius = math.sqrt(dx*dx+dy*dy)
  1414.         cv = view.canvas()
  1415.         cv.pencolor = self.color
  1416.         cv.brushstyle = BS_CLEAR
  1417.         cv.ellipse(cx-radius, cy-radius, cx+radius+1, cy+radius+1)
  1418.         cv.line(mX, cy, cx-radius, cy)
  1419.         cv.line(cx, mY, cx, cy-radius)
  1420.         cv.line(cx+radius, cy, X, cy)
  1421.         cv.line(cx, cy+radius, cx, Y)
  1422.  
  1423.       #
  1424.       # The commented out version below draws a box instead of a circle
  1425.       # - but this box and the polyhedron borders often tend to get
  1426.       # in the way of each other
  1427.       #
  1428.  
  1429.       # def line1(x1,y1,z1,x2,y2,z2, line=cv.line, proj=view.proj):
  1430.       #     line(proj(x1,y1,z1), proj(x2,y2,z2))
  1431.       #
  1432.       # cv.pencolor = self.color
  1433.       # mX, mY, mZ = self.bmin.tuple
  1434.       # X, Y, Z = self.bmax.tuple
  1435.       # line1(mX,mY,mZ,  X,mY,mZ)
  1436.       # line1(mX,mY, Z,  X,mY, Z)
  1437.       # line1(mX, Y,mZ,  X, Y,mZ)
  1438.       # line1(mX, Y, Z,  X, Y, Z)
  1439.       # line1(mX,mY,mZ, mX, Y,mZ)
  1440.       # line1(mX,mY, Z, mX, Y, Z)
  1441.       # line1( X,mY,mZ,  X, Y,mZ)
  1442.       # line1( X,mY, Z,  X, Y, Z)
  1443.       # line1(mX,mY,mZ, mX,mY, Z)
  1444.       # line1(mX, Y,mZ, mX, Y, Z)
  1445.       # line1( X,mY,mZ,  X,mY, Z)
  1446.       # line1( X, Y,mZ,  X, Y, Z)
  1447.  
  1448.  
  1449.  
  1450. #
  1451. # Utility functions to multiselect objects.
  1452. #
  1453.  
  1454. def findnextobject(choice):
  1455.     #
  1456.     # To (multi-)select an object when Ctrl is down, we select
  1457.     # the first object whose state differs from the previous
  1458.     # objects' state
  1459.     #
  1460.     prev = None
  1461.     for z,obj,xtra in choice:
  1462.         sel = obj.selected
  1463.         #
  1464.         # if the state of this object differs from the previous'
  1465.         #
  1466.         if sel != prev:
  1467.             if prev is None:
  1468.                 #
  1469.                 # No previous, this is actually the first object.
  1470.                 #
  1471.                 prev = sel
  1472.             else:
  1473.                 #
  1474.                 # We got the object we wanted.
  1475.                 #
  1476.                 return obj
  1477.     #
  1478.     # All objects have the same state, so return the first one.
  1479.     #
  1480.     return choice[0][1]
  1481.  
  1482.  
  1483. def findlastsel(choice,keep=0):
  1484.     #
  1485.     # Find the last selected object in the choice.
  1486.     #
  1487.     for i in range(len(choice), 0, -1):
  1488.         if choice[i-1][1].selected:
  1489.             if keep:
  1490.                 return i-1
  1491.             else:
  1492.                 return i
  1493.     return 0
  1494.  
  1495.  
  1496.  
  1497. #
  1498. # Creation of flat 3D views that rotate with the mouse.
  1499. #
  1500.  
  1501. def z_recenter(view3d, list):
  1502.     bbox = quarkx.boundingboxof(list)
  1503.     if bbox is None: return
  1504.     bmin, bmax = bbox
  1505.     view3d.info["center"] = (bmin+bmax)*0.5
  1506.     # bmin = bmin - quarkx.vect(32,32,32)
  1507.     # bmax = bmax + quarkx.vect(32,32,32)
  1508.     bmin = bmin - quarkx.vect(8,8,8)
  1509.     bmax = bmax + quarkx.vect(8,8,8)
  1510.     box1 = []
  1511.     for x in (bmin.x,bmax.x):      # all 8 corners of the bounding box
  1512.         for y in (bmin.y,bmax.y):
  1513.             for z in (bmin.z,bmax.z):
  1514.                 box1.append(view3d.proj(x,y,z))
  1515.     bmin = min(box1).z
  1516.     bmax = max(box1).z
  1517.     view3d.info["sfx"] = (bmax-bmin)*0.5 / view3d.info["scale"]
  1518.     view3d.depth = (bmin, bmax+bmax-bmin)
  1519.     
  1520.  
  1521. def flat3Dview(view3d, layout, selonly=0):
  1522.  
  1523.     #
  1524.     # "localsetprojmode": Set the projection attributes and then automatically Z-recenter.
  1525.     #
  1526.     if selonly:
  1527.         def localsetprojmode(view, layout=layout):
  1528.             defsetprojmode(view)
  1529.             z_recenter(view, layout.explorer.sellist)
  1530.     else:
  1531.         def localsetprojmode(view, layout=layout):
  1532.             defsetprojmode(view)
  1533.             z_recenter(view, [layout.editor.Root])
  1534.  
  1535.     view3d.viewmode = "tex"
  1536.     view3d.flags = view3d.flags &~ (MV_HSCROLLBAR | MV_VSCROLLBAR)
  1537.     view3d.info = {"type": "2D",
  1538.                    "scale": 2.0,
  1539.                    "angle": -0.7,
  1540.                    "vangle": 0.3,
  1541.                    "custom": localsetprojmode,
  1542.                    "noclick": None,
  1543.                    "mousemode": Rotator2D,
  1544.                    "center": quarkx.vect(0,0,0),
  1545.                    "sfx": 0 }
  1546.     if selonly:
  1547.         layout.editor.setupview(view3d, layout.editor.drawmapsel)
  1548.     else:
  1549.         layout.editor.setupview(view3d)
  1550.     #setprojmode(view3d)
  1551.     return view3d
  1552.  
  1553. # ----------- REVISION HISTORY ------------
  1554. #
  1555. #
  1556. #$Log: qhandles.py,v $
  1557. #Revision 1.9  2002/05/18 09:51:56  tiglari
  1558. #support Radiant-style dragging for frozen selections
  1559. #
  1560. #Revision 1.8  2001/04/26 22:45:03  tiglari
  1561. #face-only selection & texture L RMB
  1562. #
  1563. #Revision 1.7  2001/04/08 02:39:37  tiglari
  1564. #usercenters now update recursively thru subobjects.  If this proves to
  1565. # be too slow for large objects, maybe it could be optimized to happen only
  1566. # at the beginning and the end of the drag.
  1567. #
  1568. #Revision 1.6  2001/04/08 00:40:31  tiglari
  1569. #'usercenter' specific updated on CenterHandle drag
  1570. #
  1571. #Revision 1.5  2001/02/25 11:22:51  tiglari
  1572. #bezier page support, transplanted with permission from CryEd (CryTek)
  1573. #
  1574. #Revision 1.4  2001/02/07 18:40:47  aiv
  1575. #bezier texture vertice page started.
  1576. #
  1577. #Revision 1.3  2000/10/10 07:37:12  tiglari
  1578. #support for circlestrafe selection
  1579. #
  1580. #Revision 1.2  2000/06/02 16:00:22  alexander
  1581. #added cvs headers
  1582. #
  1583. #
  1584. #